﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Conan.Options;
using ConanLib;
using Fortuitus.Core;
using Smrf.NodeXL.Core;
using Smrf.AppLib;

namespace Sword
{
    class Program
    {
        static void Main(string[] args)
        {
            bool _showHelp = false;
            string _dataFilePath = null;
            string _correlationMatrixFilePath = null;
            string _identifierFilePath = null;
            string _outDir = Environment.CurrentDirectory;
            int _graphDepth = 1;
            double _minPCC = 0.9;
            OptionSet os = new OptionSet {
                            //{"r|correlation=", "The name of the {CORRELATION ALGORITHM} used. Default Pearson Correlation. The other option is Spearman",     v => _correlation = v },
   	                        {"d|datafile=", "The path to the data {FILE} that contains the original data.",     v => _dataFilePath = v },
                            {"m|matrixfile=", "The path to the correlation matrix output {FILE} from COANAN.",     v => _correlationMatrixFilePath = v },
                            {"i|idfile=", "The path to a {FILE} with a lis of identifier. One Id per line.",     v => _identifierFilePath = v },

                            //{"a|sideload=", "The path of the input {FILE} that contains prprocessed relationships.",     v => _sideLoadFile = v },
                            {"o|outdir=", "The path of the {OUTPUT_DIRECTORY} to save the results. Default is the current directory.",     v => _outDir = v },
                            {"g|depth=", "Defines the graph {DEPTH} for genrating the network graph around the analyte of interested.", g => {_graphDepth = int.Parse(g);}  },
                            //{ "t|take=", "Takes top {LINECOUNT} from the input file. Will be apllied after skip. Zero means read all. The default is 0.", v => {_take = int.Parse(v);}  },
                            //{ "f|foldchange=", "The {FOLDCHANGE} treshhold for pre-filtering the data. The default is 1 for no pre-filtering.",  v => {foldChange = double.Parse(v);}  } ,
                            //{ "g|log2transformation=",  "An {BOOLEAN} value that indicates whether to apply a log2 transformation or not. The default is false.",   v => {_log2Transform = bool.Parse(v);}  },
                            { "c|cc=",  "The minimum {PCC} or {SCC} for building the network. The default is 0.9",   v => {_minPCC = double.Parse(v);}  },
                            //{ "l|minvectorlength=",  "The minimum length of vector included for network build. Default is 1",   v => {_minVectorLength = int.Parse(v);}  },
                            //{ "u|subnetworks=", "A {BOOLEAN} value that indicates whether to cluster subnetworks",  v => {_subNetworks = bool.Parse(v);}  },
                            //{ "v|outsubnetworks=", "A {BOOLEAN} value that indicates whether to output individual files for each subnetwork",  v => {_outputSubnetworks = bool.Parse(v);}  },
                            //{ "e|minvertexfilter=", "A {NUMBER} to filter the output of networks with minimal number of vertices. The default is 1 and so that all networks are written.",  v => {_outFilter = int.Parse(v);}  },
                            //{ "x|graphics=", "A {BOOLEAN} value that indicates whether to generate graphics for each network.",  v => {_graphics = bool.Parse(v);}  },
                            //{ "y|layout=", "A {TEXT} value that indicates which layout to use for graph generation. Default fmr - FruchtermanReingold\r\n"  + layoutLags,  v => {_la = (LayoutAlgorithm)Enum.Parse(typeof(LayoutAlgorithm), v.ToUpper());}  },
                            //{ "d|dpi=", "The targrt {DPI} value to render graphics.",  v => {_dpi = Int32.Parse(v);}  },
                           { "h|help",  "show this message and exit",                v => _showHelp = v != null }
            };

            try
            {

                List<string> extra;
                extra = os.Parse(args);
                if (_showHelp)
                {
                    ShowHelp(os);
                    return;
                }
                if (string.IsNullOrEmpty(_dataFilePath))
                {
                    Console.Write("SWORD: ");
                    Console.WriteLine("No data file specified.");
                    Console.WriteLine("Try sword --help' for more information.");
                    //  Console.WriteLine("...press enter to quit");
                    // Console.ReadLine();
                    return;
                }
                if (string.IsNullOrEmpty(_correlationMatrixFilePath))
                {
                    Console.Write("SWORD: ");
                    Console.WriteLine("No matrix file specified.");
                    Console.WriteLine("Try sword --help' for more information.");
                    //  Console.WriteLine("...press enter to quit");
                    // Console.ReadLine();
                    return;
                }
                if (string.IsNullOrEmpty(_identifierFilePath))
                {
                    Console.Write("SWORD: ");
                    Console.WriteLine("No id file specified.");
                    Console.WriteLine("Try sword --help' for more information.");
                    //  Console.WriteLine("...press enter to quit");
                    // Console.ReadLine();
                    return;
                }
            }
            catch (OptionException e)
            {
                Console.Write("SWORD: ");
                Console.WriteLine(e.Message);
                Console.WriteLine("Try sword --help' for more information.");
                //   Console.WriteLine("...press enter to quit");
                //Console.ReadLine();
                return;
            }

            Run(_dataFilePath, _correlationMatrixFilePath, _identifierFilePath, _outDir, _minPCC, _graphDepth);
        }

        private static void Run(string _dataFilePath, string _correlationMatrixFilePath, string _identifierFilePath, string outDir, double minCC, int graphDepth = 1)
        {
            ColorMapper cm = new ColorMapper();
            cm.Init();

            Console.WriteLine("reading correlation matrix: {0}", _correlationMatrixFilePath);
            Dictionary<string, int> nameIndex;
            var allCorreelations = Pipeline.LoadCorrelationMatrix(_correlationMatrixFilePath, double.MinValue, out nameIndex);
            var filteredCorrelations = allCorreelations.Where(x => Math.Abs(x.RelationshipValue) >= minCC).ToList();
            
            
            Console.WriteLine("reading data file: {0}", _identifierFilePath);
            var data = ReadDataFile(_dataFilePath);

            Console.WriteLine("reading ids: {0}", _identifierFilePath);
            var names = File.ReadAllLines(_identifierFilePath);
           
            if (!Directory.Exists(outDir))
                Directory.CreateDirectory(outDir);
            var types = data.Select(x => x.GetValue<string>("Type")).Distinct();
            foreach (var t in types)
            {
                cm.Map(t);
            }
            Console.WriteLine("");
            Console.WriteLine("");
            foreach (var name in names)
            {
                Console.WriteLine("processing item {0}", name);
                if (nameIndex.ContainsKey(name))
                {
                    Console.WriteLine("building correlation hierarchy up to level {0}", graphDepth);
                    int id = nameIndex[name];
                    List<Tuple<int, double, IVector, double>> lines = new List<Tuple<int, double, IVector, double>>();
                    var hirachy = GetRelationHirachy(new List<int> { id }, filteredCorrelations, graphDepth);
                    hirachy.Insert(0, new List<int> { id });
                    if (hirachy.Count() > 0)
                    {
                        Console.WriteLine("generating graph");
                        Cluster c = new Cluster();
                        c.Id = 1;
                        for (int i = 0; i < hirachy.Count; i++)// (var item in hirachy)
                        {
                            foreach (var rid in hirachy[i])
                            {
                                var citems = filteredCorrelations.Where(x => x.Feature1.GetValue<int>("Id") == rid || x.Feature2.GetValue<int>("Id") == rid).ToList();
                                foreach (var cit in citems)
                                {
                                    if (!c.Items.Contains(cit))
                                    {
                                        c.Items.Add(cit);
                                    }
                                }
                            }
                        }
                        List<int> allIds = new List<int>();
                        allIds.Add(id);
                        foreach (var item in hirachy)
                        {
                            allIds.AddRange(item);
                        }
                        var toRemove = c.Items.Where(x => !allIds.Contains(x.Feature1.GetValue<int>("Id")) || !allIds.Contains(x.Feature2.GetValue<int>("Id"))).ToList();
                        foreach (var item in toRemove)
                        {
                            c.Items.Remove(item);
                        }

                        if (c.Items.Count > 0)
                        {
                            var network = NetworkHelper.LoadGraphFromClusters(new List<Cluster> { c }, cm, c.Items.Min(x => x.RelationshipValue), c.Items.Max(x => x.RelationshipValue), false);




                            float levelDist = 0;
                            float levelStep = 1f / ((float)hirachy.Count - 1);
                            levelDist += levelStep;
                            for (int i = 1; i < hirachy.Count; i++)
                            {
                                var hids = hirachy[i];
                                float step = 360f / (hids.Count);
                                float angle = 0;
                                foreach (var hid in hids)
                                {
                                    var vert = network.Vertices.Single(x => (int)x.GetValue("Id") == hid);
                                    vert.SetValue(ReservedMetadataKeys.PolarLayoutCoordinates, new SinglePolarCoordinates(levelDist, angle));
                                    angle += step;
                                }
                                levelDist += levelStep;
                            }

                            var center = network.Vertices.Single(x => (int)x.GetValue("Id") == id);
                            center.SetValue(ReservedMetadataKeys.PolarLayoutCoordinates, new SinglePolarCoordinates(0f, 0));
                            center.SetValue(ReservedMetadataKeys.PerVertexRadius, 8f);


                            string nfileName = name + "_graph.txt";
                            string noutFilePath = Path.Combine(outDir, nfileName);
                            foreach (var item in Path.GetInvalidPathChars())
                            {
                                nfileName = nfileName.Replace(item, '_');
                            }

                            NetworkHelper.GenerateGraphImage(network, noutFilePath, 300, LayoutAlgorithm.POLAR);
                        }

                    }

                    Console.WriteLine("generating data file");
                    var corelatedToCurrent = allCorreelations.Where(x => x.Feature1.GetValue<int>("Id") == id ||  x.Feature2.GetValue<int>("Id") == id).AsParallel().ToList();
                    foreach (var d in data.Where(x => x.GetValue<string>("Name") != name).ToList())
                    {
                        double val = double.NaN;
                        string name2 = d.GetValue<string>("Name");
                        int id2 = -1;
                        if (nameIndex.ContainsKey(name2))
                        {
                            id2 = nameIndex[name2];

                            var rel = corelatedToCurrent.SingleOrDefault(x=> x.Feature2.GetValue<int>("Id") == id2
                                || x.Feature1.GetValue<int>("Id") == id2);
                            if (rel != null)
                                val = rel.RelationshipValue;
                        }

                        double hirachyLevel = double.NaN;
                        for (int i = 1; i < hirachy.Count; i++)
                        {
                            if (hirachy[i].Contains(id2))
                                hirachyLevel = i;
                        }

                        Tuple<int, double, IVector, double> tuple = new Tuple<int, double, IVector, double>(id2, val, d, hirachyLevel);
                        lines.Add(tuple);
                    }
                    string fileName = name + ".txt";
                    foreach (var item in Path.GetInvalidPathChars())
                    {
                        fileName = fileName.Replace(item, '_');
                    }

                    lines = lines.OrderByDescending(x => x.Item2).ToList();
                   

                    var idVector = data.Single(x => x.GetValue<string>("Name") == name);
                    Tuple<int, double, IVector, double> selftuple = new Tuple<int, double, IVector, double>(id, 1, idVector, 0);


                    lines.Insert(0, selftuple);
                 
                    string outFilePath = Path.Combine(outDir, fileName);
                    StreamWriter sw = new StreamWriter(File.Create(outFilePath));
                    sw.Write("{0}\t{1}\t{2}\t{3}", "Id", "CorrelationCoefficient", "Hierarchy", "Type", "Description");
                    foreach (var item in lines)
                    {
                        sw.Write("{0}\t{1}\t{2}\t{3}", item.Item3.GetValue<string>("Name"), item.Item2, item.Item4, item.Item3.GetValue<string>("Type"), item.Item3.GetValue<string>("Description"));
                        foreach (var v in item.Item3)
                        {
                            sw.Write("\t{0}", v);
                        }
                        sw.WriteLine();
                    }
                    sw.Flush();
                    sw.Close();

                    Console.WriteLine("generating heat map");
                    try
                    {
                        var vectors = lines.Select(x => x.Item3).ToList();
                        vectors.Insert(0, selftuple.Item3);
                        NetworkHelper.GenerateHeatMap(vectors, outFilePath.Replace("-POSITIVE.txt", ".png"), 100);
                        if (lines.Count > 99)
                        {
                            vectors.Insert(0, selftuple.Item3);
                            vectors = lines.Skip(lines.Count - 99).OrderBy(x => x.Item2).Select(x => x.Item3).ToList();

                        }
                        else
                        {
                            vectors = lines.OrderBy(x => x.Item2).Select(x => x.Item3).ToList();
                            vectors.Remove(selftuple.Item3);
                            vectors.Insert(0, selftuple.Item3);
                        }
                        NetworkHelper.GenerateHeatMap(vectors, outFilePath.Replace("-NEGATIVE.txt", ".png"), 100);
                    }
                    catch
                    {

                    }
                    Console.WriteLine("");
                    Console.WriteLine("");
                }
                else
                {
                    Console.WriteLine("Could find {0} in correlation matrix", name);
                }

            }

            Console.WriteLine("done...");

        }

        private static List<List<int>> GetRelationHirachy(List<int> ids, IEnumerable<ClusterItem> items, int depth)
        {
            List<List<int>> ret = new List<List<int>>();
            List<ClusterItem> relatedItems = new List<ClusterItem>();
            List<ClusterItem> nextRoundItems = new List<ClusterItem>(items);
            foreach (var id in ids)
            {
                var corList = items.Where(x =>(x.Feature1.GetValue<int>("Id") == id || x.Feature2.GetValue<int>("Id") == id)).ToList();


                //   nextRoundItems = nextRoundItems.Where(x => corList.Contains(x) == false).AsParallel().ToList();
                foreach (var item in corList)
                {
                    nextRoundItems.Remove(item);
                    if (!relatedItems.Contains(item))
                        relatedItems.Add(item);
                }
            }

            var relatedIds = relatedItems.Select(x => x.Feature1.GetValue<int>("Id")).ToList();
            relatedIds.AddRange(relatedItems.Select(x => x.Feature2.GetValue<int>("Id")));
            relatedIds = relatedIds.Distinct().ToList();
            foreach (var item in ids)
            {
                relatedIds.Remove(item);
            }
            if (relatedIds.Count > 0)
                ret.Add(relatedIds);
            if (depth > 1)
            {
                if (relatedIds.Count > 0 && nextRoundItems.Count > 0)
                {
                    var nextlevels = GetRelationHirachy(relatedIds, nextRoundItems, depth - 1);
                    if (nextlevels.Count > 0)
                        ret.AddRange(nextlevels);
                }
            }
            return ret;
        }

        public static IOrderedList<EditableVector> ReadDataFile(string filePath)
        {
            Console.WriteLine("read file {0}", filePath);

            List<string> nameTracker = new List<string>();
            var lines = File.ReadAllLines(filePath);

            int skipLines = 1; //always skip the header 

            OrderedList<EditableVector> features = new OrderedList<EditableVector>();


            lines = lines.Skip(skipLines).ToArray();

            int currentId = 1;
            foreach (var line in lines) //skip the header or more if specified
            {
                var vector = NetworkHelper.CreateVector(line, currentId);
                var name = vector.GetValue<string>("Name");
                if (!nameTracker.Contains(name))
                {
                    features.Add(vector);
                    nameTracker.Add(name);
                    currentId++;
                }
                else
                {
                    var c = Console.ForegroundColor;
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    Console.WriteLine("Warning:");
                    Console.WriteLine("double entry for {0} found. This feature will be skipped", name);
                    Console.ForegroundColor = c;
                }
            }
            return features;
        }

        private static void ShowHelp(OptionSet os)
        {
            Console.WriteLine("Usage: sword -d datafilename -m matrixfile -i idfile");
            Console.WriteLine();
            Console.WriteLine("Options:");
            os.WriteOptionDescriptions(Console.Out);
        }
    }
}
